package com.pf.controller;

import com.google.common.collect.ImmutableMap;
import com.pf.dto.BaseResult;
import com.pf.dto.ClientDTO;
import com.pf.utils.BaseResultUtils;
import com.pf.utils.PwdEncryptUtils;
import com.pf.utils.SignUtils;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;

/**
 * @author PanFei
 * @version 1.0.0
 * @createTime 2021/9/8
 */
@Slf4j
@RestController
public class OrderController {

    /**
     * 为客户端生成的唯一标识/密钥
     */
    private static final Map<String, Client> APP_ID_SECRET_MAP = new HashMap<>();

    static {
        APP_ID_SECRET_MAP.put("client_1001", Client.builder().appId("client_1001").appSecret("abc123").appSecretSalt("123456").build());
    }

    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder
    public static class Client {
        /**
         * 客户端唯一标志
         */
        private String appId;
        /**
         * 加密的客户端密码
         */
        private String appSecret;
        /**
         * 加密的客户端密码的盐值
         */
        private String appSecretSalt;
    }

    /**
     * 模拟生成token
     * <p>
     * 实际开发中可记住jwt生成token
     *
     * @param clientDTO
     * @return
     */
    @PostMapping("/token/create")
    public BaseResult<String> createToken(@RequestBody ClientDTO clientDTO) {
        log.info(">>>>> OrderController - createToken reqParams：clientDTO={} ", clientDTO);
        // 校验客户端密码
        String appId = clientDTO.getAppId();
        Client client = Optional.ofNullable(APP_ID_SECRET_MAP.get(appId)).orElse(null);
        if (client == null) {
            log.error(">>>>> OrderController - createToken 客户端不存在！appId={}", appId);
            throw new RuntimeException("客户端不存在！");
        }
        String checkDecryptPwd = PwdEncryptUtils.md5AndSha(client.appSecret + client.getAppSecretSalt());
        if (!clientDTO.getDecryptAppSecret().equals(checkDecryptPwd)) {
            log.error(">>>>> OrderController - createToken 客户端密码错误！appId={},decryptAppSecret={}", appId, clientDTO.getDecryptAppSecret());
            throw new RuntimeException("客户端密码错误！");
        }

        log.info(">>>>> OrderController - createToken 客户端用户密码校验通过！");

        // 生成token
        int randomStr = (int) (Math.random() * 900 + 100);
        String token = UUID.randomUUID().toString().replace("-", "") + randomStr;
        log.info(">>>>> OrderController - createToken 生成的token={} ", token);
        return BaseResultUtils.success(token);
    }

    /**
     * 模拟下单操作
     * <p>
     * 注意点：
     * 1.复杂的接口 reqParams，reqHeaders，reqBody都会有值
     * 2.接收业务参数必须用String类型接收，不能用自定义实体类型接收
     * 原因：我们生成签名规则是将业务参数转为json字符串，然后拼接在reqParams后面，再利用 MD5 生成16进制的字符串，
     * 该接口参数中嵌套自定义的引用类型，如果调用方实体类参数顺序和我们定义顺序不一样，生成的json字符串 reqBody 也会不一样，
     * 这样生成的签名sign也会不一样。此时校验sign时，我们生成的sign会和客户端生成的不一样，此时会验签失败。
     * 因此必须原封不到的接口调用方的业务参数 reqBody，然后生成sign，再与客户端传过来的sign做比较验签。
     *
     * @param appId
     * @param requestId
     * @param timestamp
     * @param sign
     * @param token
     * @param reqBodyStr
     * @return
     */
    @PostMapping("/order")
    public BaseResult order(@RequestParam("appId") String appId,
                            @RequestParam("requestId") String requestId,
                            @RequestParam("timestamp") String timestamp,
                            @RequestParam("sign") String sign,
                            @RequestHeader("token") String token,
                            @RequestBody String reqBodyStr) throws Exception {
        log.info(">>>>> OrderController - order ： appId={},requestId={},timestamp={},sign={}", appId, requestId, timestamp, sign);
        log.info(">>>>> OrderController - order ： token={}", token);
        log.info(">>>>> OrderController - order ： reqBodyStr={}", reqBodyStr);

        // 验签
        String appSecret = APP_ID_SECRET_MAP.get(appId).getAppSecret();
        Map<String, String> reqParams = ImmutableMap.of("appId", appId, "requestId", requestId, "timestamp", timestamp, "sign", sign);
        String checkSign = SignUtils.sign(appSecret, reqParams, reqBodyStr);
        log.info(">>>>> OrderController - order ：生成的checkSign={}", checkSign);
        if (sign.equals(checkSign)) {
            log.info(">>>>> OrderController - order 验签成功！");
        } else {
            log.info(">>>>> OrderController - order 验签失败！");
        }

        log.info(">>>>> OrderController - order ：开始处理业务逻辑");

        return BaseResultUtils.success();
    }
}
